<?php
namespace App\Repositories;

use App\Http\Requests\AnnounceRequest;
use App\Models\Quiz;
use App\Models\QuizItem;
use App\Models\Bet;
use App\Exceptions\LocalizationException;
use Illuminate\Support\Facades\DB;
use App\Jobs\ProcessAnnounce;

class QuizRepository
{
    const QUIZ_STATUS_NORMAL = 2;
    const QUIZ_STATUS_CLOSED = 0;
    const QUIZ_STATUS_FINISHED = 3;
    const QUIZ_STATUS_PROCESSING = 1;
    const QUIZ_STATUS_STOPPED = 4;

    const QUIZ_ITEM_STATUS_NORMAL = 1;
    const QUIZ_ITEM_STATUS_CLOSED = 0;

    const TYPE_NORMAL = 1;
    const TYPE_WORLDCUP = 2;

    const QUIZ_ITEM_RESULT_WIN = 'win';
    const QUIZ_ITEM_RESULT_LOSE = 'lose';
    const QUIZ_ITEM_RESULT_DRAW = 'draw';
    const QUIZ_ITEM_RESULT_WIN_HALF = 'win-half';
    const QUIZ_ITEM_RESULT_LOSE_HALF = 'lose-half';
    const QUIZ_ITEM_RESULT_DEFAULT = self::QUIZ_ITEM_RESULT_DRAW;

    const BET_STATUS_PROCESSING = 1;
    const BET_STATUS_PROCESSED = 2;
    const BET_STATUS_CANCELED = 0;
    const BET_STATUS_FINISHED = 3;

    public static $quizItemResults = [
        self::QUIZ_ITEM_RESULT_WIN,
        self::QUIZ_ITEM_RESULT_LOSE,
        self::QUIZ_ITEM_RESULT_DRAW,
        self::QUIZ_ITEM_RESULT_WIN_HALF,
        self::QUIZ_ITEM_RESULT_LOSE_HALF,
    ];

    public static $quizItemWinResults = [
        self::QUIZ_ITEM_RESULT_WIN,
        self::QUIZ_ITEM_RESULT_WIN_HALF,
    ];

    public static $types = [
        self::TYPE_NORMAL => 'Normal',
        self::TYPE_WORLDCUP => 'WorldCup',
    ];

    public static $status = [
        self::QUIZ_STATUS_CLOSED => 'generic.quiz_status_closed',
        self::QUIZ_STATUS_PROCESSING => 'generic.quiz_status_processing',
        self::QUIZ_STATUS_NORMAL => 'generic.quiz_status_normal',
        self::QUIZ_STATUS_FINISHED => 'generic.quiz_status_finished',
        self::QUIZ_STATUS_STOPPED => 'generic.quiz_status_stopped',
    ];


    /**
     * 下注
     *
     * @param QuizItem $quizItem
     * @param int $copper
     * @throws LocalizationException
     */
    public function bet(QuizItem $quizItem, int $copper)
    {
        // todo: 检查竞猜是否可用
        if ($quizItem->status != self::QUIZ_ITEM_STATUS_NORMAL
            || $quizItem->quiz->status != self::QUIZ_STATUS_NORMAL) {
            throw new LocalizationException('quiz is\'t allowed to participate');
        }

        // todo: 检查铜币是否足够以及是否超出限制
        if ($quizItem->quiz->currentUserAccountInThisGroup->copper < $copper) {
            throw new LocalizationException('insufficient copper coins');
        }

        // todo: 判断是否结束下注
        $stopAt = string_to_datetime($quizItem->quiz->stop_at);
        if ($stopAt <= now()) {
            throw new LocalizationException('stop betting at :datetime', [
                'datetime' => $stopAt->format('Y-m-d H:i:s')
            ]);
        }
        $betCoppers = $quizItem->quiz
            ->currentBets()
            ->whereIn('status', [
                self::BET_STATUS_PROCESSED,
                self::BET_STATUS_PROCESSING,
                self::BET_STATUS_FINISHED
            ])
            ->sum('copper');
        if (($copper + $betCoppers) > (int)$quizItem->quiz->copper_limit) {
            throw new LocalizationException('exceed limit :limit :betted', [
                'limit' => $quizItem->quiz->copper_limit,
                'betted' => $copper + $betCoppers
            ]);
        }

        DB::transaction(function () use ($quizItem, $copper) {
            $bet = new Bet();
            $bet->copper = $copper;
            $bet->quizItem()->associate($quizItem);
            $bet->saveOrFail();
        });

        $quizItem->removeCache('count');
    }

    /**
     * 发布竞猜结果
     *
     * @param Quiz $quiz
     * @param AnnounceRequest $request
     * @throws LocalizationException
     */
    public function announce(Quiz $quiz, AnnounceRequest $request)
    {
        if ($quiz->status != self::QUIZ_STATUS_NORMAL) {
            throw new LocalizationException('quiz status error');
        }

        DB::transaction(function () use ($quiz, $request) {
            $affectedRows = Quiz::where('id', $quiz->id)
                ->where('status', self::QUIZ_STATUS_NORMAL)
                ->update([
                    'result' => $request->result,
                    'status' => self::QUIZ_STATUS_PROCESSING,
                ]);
            if (!$affectedRows) {
                throw new LocalizationException('failed to modify the status');
            }

            $data = $request->post('items', []);
            $quiz->items->each(function ($item) use ($data) {
                $i = array_first($data, function ($row, $key) use ($item) {
                    return $row['id'] == $item->id;
                });
                $item->result = $i ? $i['result'] : self::QUIZ_ITEM_RESULT_DEFAULT;
                $item->saveOrFail();
            });
            ProcessAnnounce::dispatch($quiz);
        });

        // 取消推送模板消息
        MessageRepository::cancelSendMessage($quiz);
    }

    /**
     * 庄家结算
     *
     * @param Quiz $quiz
     */
    public static function settlementForCreater(Quiz $quiz)
    {
        $total = (float)Bet::where('quiz_id', $quiz->id)
            ->where('status', QuizRepository::BET_STATUS_FINISHED)
            ->sum(DB::raw('copper - copper_result'));

        DB::transaction(function () use ($quiz, $total) {
            $groupUser = $quiz->createrAccountInThisGroup;
            $groupUser->copper = DB::raw("copper + $total");
            $groupUser->save();

            $quiz->status = QuizRepository::QUIZ_STATUS_FINISHED;
            $quiz->save();

            StatementRepository::recordAnnounce($quiz, $total);
        });
    }

    /**
     * 玩家结算
     *
     * @param Bet $bet
     * @param QuizItem $quizItem
     */
    public static function settlementForPlayer(Bet $bet, QuizItem $quizItem)
    {
        // 计算赔率
        $odds = QuizRepository::calculateOdds($bet->odds, $quizItem->result);
        $balance = (int)($bet->copper * $odds);

        $bet->result = $quizItem->result;
        $bet->copper_result = $balance;
        $bet->status = QuizRepository::BET_STATUS_FINISHED;

        DB::transaction(function () use ($bet) {
            $bet->save();
        });
    }

    /**
     * @param $odds
     * @param $result
     * @return float|int
     */
    public static function calculateOdds($odds, $result)
    {
        switch ($result) {
            case self::QUIZ_ITEM_RESULT_WIN:
                break;
            case self::QUIZ_ITEM_RESULT_LOSE:
                $odds = 0;
                break;
            case self::QUIZ_ITEM_RESULT_WIN_HALF:
                $odds = ($odds-1)/2+1;
                break;
            case self::QUIZ_ITEM_RESULT_LOSE_HALF:
                $odds = 0.5;
                break;
            case self::QUIZ_ITEM_RESULT_DRAW:
            default:
                $odds = 1;
                break;
        }
        return $odds;
    }

    /**
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function findCurrentUserQuizzes()
    {
        return Quiz::with(['group', 'creater'])
            ->join('quiz_user', 'quiz_user.quiz_id', '=', 'quizzes.id')
            ->where('quiz_user.user_id', request()->user()->id)
            ->select(['quizzes.*'])
            ->orderBy('quiz_user.updated_at', 'desc')
            ->paginate();
    }
}